 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.cheatsheets.composite.parser;

 import java.io.IOException ;
 import java.io.InputStream ;
 import java.net.URL ;

 import javax.xml.parsers.DocumentBuilder ;
 import javax.xml.parsers.DocumentBuilderFactory ;

 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.ui.internal.cheatsheets.Messages;
 import org.eclipse.ui.internal.cheatsheets.composite.model.AbstractTask;
 import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel;
 import org.eclipse.ui.internal.cheatsheets.composite.model.EditableTask;
 import org.eclipse.ui.internal.cheatsheets.composite.model.TaskGroup;
 import org.eclipse.ui.internal.cheatsheets.data.CheatSheetParserException;
 import org.eclipse.ui.internal.cheatsheets.data.IParserTags;
 import org.eclipse.ui.internal.cheatsheets.data.ParserStatusUtility;
 import org.w3c.dom.Document ;
 import org.w3c.dom.NamedNodeMap ;
 import org.w3c.dom.Node ;
 import org.w3c.dom.NodeList ;
 import org.xml.sax.InputSource ;
 import org.xml.sax.SAXException ;
 import org.xml.sax.SAXParseException ;

 public class CompositeCheatSheetParser implements IStatusContainer {
     
     private DocumentBuilder documentBuilder;
     
     private IStatus status;
     
     private int nextTaskId = 0;
     
     
     /**
      * Gets the status of the last call to parseGuide
      */
     
     public IStatus getStatus() {
         return status;
     }
     
     /**
      * Returns the DocumentBuilder to be used by composite cheat sheets.
      */
     public DocumentBuilder getDocumentBuilder() {
         if(documentBuilder == null) {
             try {
                 documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
             } catch (Exception e) {
                 addStatus(IStatus.ERROR, Messages.ERROR_CREATING_DOCUMENT_BUILDER, e);
             }
         }
         return documentBuilder;
     }
         
     public void addStatus(int severity, String message, Throwable exception) {
         status = ParserStatusUtility.addStatus(status, severity, message, exception);
     }
     
     /**
      * Parse a composite cheat sheet from a url. The parser status will be set as a result
      * of this operation, if the status is IStatus.ERROR the parser returns null
      * @param url The url of the input
      * @return A valid composite cheat sheet or null if there was an error
      */
     public CompositeCheatSheetModel parseGuide(URL url) {
         status = Status.OK_STATUS;
         if(url == null) {
             String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object [] {""})); //$NON-NLS-1$
 addStatus(IStatus.ERROR, message, null);
             return null;
         }

         InputStream is = null;

         try {
             is = url.openStream();

             if (is == null) {
                 String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object [] {url.getFile()}));
                 addStatus(IStatus.ERROR, message, null);
                 return null;
             }
         } catch (Exception e) {
             String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object [] {url.getFile()}));
             addStatus(IStatus.ERROR, message, e);
             return null;
         }
         
         Document document;
         String filename = url.getFile();
         try {
             InputSource inputSource = new InputSource (is);
             document = getDocumentBuilder().parse(inputSource);
         } catch (IOException e) {
             String message = NLS.bind(Messages.ERROR_OPENING_FILE_IN_PARSER, (new Object [] {filename}));
             addStatus(IStatus.ERROR, message, e);
             return null;
         } catch (SAXParseException spe) {
             String message = NLS.bind(Messages.ERROR_SAX_PARSING_WITH_LOCATION, (new Object [] {filename, new Integer (spe.getLineNumber()), new Integer (spe.getColumnNumber())}));
             addStatus(IStatus.ERROR, message, spe);
             return null;
         } catch (SAXException se) {
             String message = NLS.bind(Messages.ERROR_SAX_PARSING, (new Object [] {filename}));
             addStatus(IStatus.ERROR, message, se);
             return null;
         } finally {
             try {
                 is.close();
             } catch (Exception e) {
             }
         }
         
         CompositeCheatSheetModel result = parseCompositeCheatSheet(document, url);
         return result;
     }

     /**
      * Parse a composite cheatsheet. The parser status will be set as a result
      * of this operation, if the status is IStatus.ERROR the parser returns null
      * @param url The url of the input. This is only used so the model can record
      * its input location
      * @param document the document to be parse
      * @return A valid composite cheat sheet or null if there was an error
      * @return
      */
     public CompositeCheatSheetModel parseCompositeCheatSheet(Document document, URL url) {
         status = Status.OK_STATUS;
         try {
             // If the document passed is null return a null tree and update the status
 if (document != null) {
                 Node rootnode = document.getDocumentElement();
                 
                 // Is the root node correct?
 if( !rootnode.getNodeName().equals(ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET )) {
                     String message = NLS.bind(Messages.ERROR_PARSING_ROOT_NODE_TYPE, (
                             new Object [] {ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET}));
                     throw new CheatSheetParserException(message);
                 }
                 
                 String name = ""; //$NON-NLS-1$
 boolean nameFound = false;
                 String explorerId = ICompositeCheatsheetTags.TREE;
                 
                 NamedNodeMap attributes = rootnode.getAttributes();
                 if (attributes != null) {
                     for (int x = 0; x < attributes.getLength(); x++) {
                         Node attribute = attributes.item(x);
                         String attributeName = attribute.getNodeName();
                         if ( attributeName != null && attributeName.equals(ICompositeCheatsheetTags.NAME)) {
                             nameFound = true;
                             name= attribute.getNodeValue();
                         }
                         if (attributeName.equals(ICompositeCheatsheetTags.EXPLORER)) {
                             explorerId= attribute.getNodeValue();
                         }
                     }
                 }
                 CompositeCheatSheetModel compositeCS = new CompositeCheatSheetModel(name, name, explorerId);
                 
                 parseCompositeCheatSheetChildren(rootnode, compositeCS);
                 
                 compositeCS.getDependencies().resolveDependencies(this);
                 
                 if (compositeCS.getRootTask() == null) {
                     addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_ROOT, null);
                 }
                 if (!nameFound) {
                     addStatus(IStatus.ERROR, Messages.ERROR_PARSING_CCS_NO_NAME, null);
                 }
                 if (status.getSeverity() != IStatus.ERROR) {
                     compositeCS.setContentUrl(url);
                     return compositeCS;
                 }
             }
             return null;
         } catch(CheatSheetParserException e) {
             addStatus(IStatus.ERROR, e.getMessage(), null);
             return null;
         }
     }

     private void parseCompositeCheatSheetChildren(Node compositeCSNode, CompositeCheatSheetModel model) {
         nextTaskId = 0;
         NodeList childNodes = compositeCSNode.getChildNodes();
         for (int index = 0; index < childNodes.getLength(); index++) {
             Node nextNode = childNodes.item(index);
             if (isAbstractTask(nextNode.getNodeName()) ) {
                 AbstractTask task = parseAbstractTask(nextNode, model);
                 if (model.getRootTask() == null ) {
                     model.setRootTask(task);
                     parseTaskChildren(nextNode, task, model);
                 } else {
                     addStatus(IStatus.ERROR, Messages.ERROR_PARSING_MULTIPLE_ROOT, null);
                 }
             }
         }
     }

     public static boolean isAbstractTask(String nodeName) {
         return nodeName == ICompositeCheatsheetTags.TASK ||
             nodeName == ICompositeCheatsheetTags.TASK_GROUP;
     }
     
     private void parseTaskChildren(Node parentNode, AbstractTask parentTask, CompositeCheatSheetModel model) {
         NodeList childNodes = parentNode.getChildNodes();
         ITaskParseStrategy strategy = parentTask.getParserStrategy();
         strategy.init();
         for (int index = 0; index < childNodes.getLength(); index++) {
             Node childNode = childNodes.item(index);
             if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                 String nodeName = childNode.getNodeName();
                 if (nodeName == IParserTags.PARAM) {
                     addParameter(parentTask, childNode.getAttributes());
                 } else if (nodeName == IParserTags.INTRO) {
                     parentTask.setDescription(MarkupParser.parseAndTrimTextMarkup(childNode));
                 } else if (nodeName == ICompositeCheatsheetTags.ON_COMPLETION) {
                     parentTask.setCompletionMessage(MarkupParser.parseAndTrimTextMarkup(childNode));
                 } else if (nodeName == ICompositeCheatsheetTags.DEPENDS_ON) {
                     parseDependency(childNode, parentTask, model);
                 } else if (CompositeCheatSheetParser.isAbstractTask(nodeName)) {
                     if (parentTask instanceof TaskGroup) {
                         AbstractTask task = parseAbstractTask(childNode, model);
                         ((TaskGroup)parentTask).addSubtask(task);
                         parseTaskChildren(childNode, task, model);
                     }
                 } else {
                     if (!strategy.parseElementNode(childNode, parentNode, parentTask, this)) {
                         String message = NLS
                         .bind(
                                 Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
                                 (new Object [] { nodeName,
                                         parentNode.getNodeName() }));
                         addStatus(IStatus.WARNING, message, null);
                     }
                 }
             }
         }
         // Check for missing attributes and add dependencies if this was a sequence
 strategy.parsingComplete(parentTask, this);
     }

     private void parseDependency(Node taskNode, AbstractTask task, CompositeCheatSheetModel model) {
         NamedNodeMap attributes = taskNode.getAttributes();
         if (attributes != null) {
              Node taskAttribute = attributes.getNamedItem(ICompositeCheatsheetTags.TASK);
              if (taskAttribute != null) {
                  String requiredTaskId = taskAttribute.getNodeValue();
                  model.getDependencies().addDependency(task, requiredTaskId);
              } else {
                  addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_ID, null);
              }
         }
     }

     private void addParameter(AbstractTask parentTask, NamedNodeMap attributes) {
         String name = null;
         String value = null;

         if (attributes != null) {
             for (int x = 0; x < attributes.getLength(); x++) {
                 Node attribute = attributes.item(x);
                 String attributeName = attribute.getNodeName();
                 if (attribute == null || attributeName == null)
                     continue;
                 if (attributeName.equals(ICompositeCheatsheetTags.NAME)) {
                     name = attribute.getNodeValue();
                 }
                 if (attributeName.equals(ICompositeCheatsheetTags.VALUE)) {
                     value= attribute.getNodeValue();
                 }
             }
         }
         if (name == null) {
             addStatus(IStatus.WARNING, Messages.ERROR_PARSING_NO_NAME, null);
             return;
         } else if (value == null) {
             addStatus(IStatus.WARNING, Messages.ERROR_PARSING_NO_VALUE, null);
             return;
         } else {
             parentTask.getParameters().put(name, value);
         }
         
     }

     private AbstractTask parseAbstractTask(Node taskNode, CompositeCheatSheetModel model) {
         AbstractTask task;
         NamedNodeMap attributes = taskNode.getAttributes();
         String kind = null;
         String name = null;
         String id = null;
         boolean skippable = false;
         if (attributes != null) {
             for (int x = 0; x < attributes.getLength(); x++) {
                 Node attribute = attributes.item(x);
                 String attributeName = attribute.getNodeName();
                 if (attribute == null || attributeName == null)
                     continue;
                 if (attributeName.equals(ICompositeCheatsheetTags.KIND)) {
                     kind = attribute.getNodeValue();
                 }
                 if (attributeName.equals(ICompositeCheatsheetTags.NAME)) {
                     name= attribute.getNodeValue();
                 }
                 if (attributeName.equals(IParserTags.ID)) {
                     id = attribute.getNodeValue();
                 }
                 if (attributeName.equals(IParserTags.SKIP)) {
                     skippable = "true".equalsIgnoreCase(attribute.getNodeValue()); //$NON-NLS-1$
 }
             }
         }

         String nodeName = taskNode.getNodeName();
         if (id == null) {
             id = autoGenerateId();
         }
         if (name == null) {
             String message = NLS.bind(Messages.ERROR_PARSING_TASK_NO_NAME, (new Object [] {nodeName}));
             addStatus(IStatus.ERROR, message, null);
         }
         task = createTask(nodeName, model, kind, id, name);
         task.setSkippable(skippable);

         if (model.getDependencies().getTask(id) != null) {
             String message = NLS.bind(Messages.ERROR_PARSING_DUPLICATE_TASK_ID, (new Object [] {id, }));
             addStatus(IStatus.ERROR, message, null);
         } else {
             model.getDependencies().saveId(task);
         }

         return task;
     }

     private AbstractTask createTask(String nodeKind, CompositeCheatSheetModel model, String kind, String id, String name) {
         AbstractTask task;
         if (ICompositeCheatsheetTags.TASK_GROUP.equals(nodeKind)) {
             task = new TaskGroup(model, id, name, kind);
         } else {
             task = new EditableTask(model, id, name, kind);
         }
         task.setCompletionMessage(Messages.COMPLETED_TASK);
         return task;
     }
     
     private String autoGenerateId() {
         return "TaskId_" + nextTaskId++; //$NON-NLS-1$
 }

 }

